/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ua.yandex.collections;

/**
 *
 * @author Mitya
 */
public class MyLinkedList implements MyList {

    private static class Node {

        private Object value;
        private Node next;
        private Node prev;

        public Node(Object value) {
            this.value = value;
        }

        public Node(Node prev, Object value, Node succ) {
            this.prev = prev;
            this.value = value;
            this.next = succ;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object newValue) {
            this.value = newValue;
        }
    }

    private Node head;
    private Node tail;
    private int size;

    public MyLinkedList() {
        this.size = 0;
        head = null;
    }

    @Override
    public void add(Object e) {
        addLast(e);
    }

    @Override
    public void add(int index, Object e) {
        checkOutOfBoundsForAdd(index);
        if (index == 0) {
            addFirst(e);
        } else {
            linkBefore(e, getNode(index));
        }

    }

    @Override
    public void addAll(Object[] c) {
        addAll(size, c);
    }

    @Override
    public void addAll(int index, Object[] c) {
        checkOutOfBoundsForAdd(index);
        if (c.length == 0) {
            System.out.println("Here");
            return;
        }
        Node previous;
        Node succesor;
        if (index == size) {
            succesor = null;
            previous = tail;
        } else {
            succesor = getNode(index);
            previous = succesor.prev;
        }

        for (Object e : c) {
            Node newNode = new Node(previous, e, null);
            if (previous == null) {
                head = newNode;
            } else {
                previous.next = newNode;
            }
            previous = newNode;
        }

        if (succesor == null) {
            tail = previous;
        } else {
            previous.next = succesor;
            succesor.prev = previous;
        }
        size += c.length;
    }

    @Override
    public Object get(int index) {
        checkOutOfBounds(index);
        Node temp = head;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        return temp.getValue();
    }

    private Node getNode(int index) {
        checkOutOfBounds(index);
        Node temp = head;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        return temp;

    }

    @Override
    public Object remove(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(String.valueOf(index));
        }
        Object result = head.getValue();
        if (index == 0) {
            result = removeFirst();
        } else if (index == size - 1) {
            result = removeLast();
        } else {
            Node temp = head;
            for (int i = 0; i < index; i++) {
                temp = temp.next;
            }
            result = temp.next.getValue();
            Node newNextNode = temp.next.next;
            temp.next = newNextNode;
            newNextNode.prev = temp;
            size--;
        }
        return result;
    }

    @Override
    public void set(int index, Object e) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(String.valueOf(index));
        }

        Node temp = head;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }

        temp.setValue(e);
    }

    @Override
    public int indexOf(Object o) {
        Node temp = head;
        int index = 0;
        while (temp != null) {
            if (temp.getValue().equals(o)) {
                return index;
            }
            index++;
            temp = temp.next;
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        Node node = head;
        while (node != null) {
            Node next = node.next;
            node.value = null;
            node.next = null;
            node.prev = null;
            node = next;
        }
        tail = null;
        head = null;
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return head == null;
    }

    @Override
    public Object[] toArray() {
        Object[] resultArray = new Object[size];
        Node temp = head;
        for (int i = 0; i < size; i++) {
            resultArray[i] = temp.getValue();
            temp = temp.next;
        }
        return resultArray;
    }

    @Override
    public String toString() {
        StringBuilder resultStringBuilder = new StringBuilder();
        Node temp = head;
        resultStringBuilder.append("[");
        while (temp != null && temp.next != null) {
            resultStringBuilder.append(temp.getValue().toString() + ", ");
            temp = temp.next;
        }
        if (temp != null) {
            resultStringBuilder.append(temp.getValue().toString());
        }
        resultStringBuilder.append("]");
        return resultStringBuilder.toString();

    }

    public void addFirst(Object e) {
        Node newNode = new Node(e);
        if (size != 0) {
            newNode.next = head;
        } else {
            tail = newNode;
        }
        head = newNode;
        size++;
    }

    public void addLast(Object e) {
        Node prevTail = tail;
        Node newNode = new Node(tail, e, null);
        tail = newNode;
        if (prevTail == null) {
            head = newNode;
        } else {
            prevTail.next = newNode;
        }
        size++;
    }

    public Object getFirst() {
        return head.getValue();
    }

    public Object getLast() {
        return tail.getValue();
    }

    public Object removeFirst() {
        if (size == 0) {
            throw new IndexOutOfBoundsException(String.valueOf(0));
        }
        Object resObject = head.getValue();
        head = head.next;
        head.prev = null;
        size--;
        return resObject;
    }

    public Object removeLast() {
        if (size == 0) {
            throw new IndexOutOfBoundsException(String.valueOf(0));
        }
        Object result = tail.getValue();
        tail = tail.prev;
        tail.next = null;
        size--;
        return result;
    }

    public MyListIterator getListIterator(int index) {
        return new MyLinkedListImpl(index);
    }

    public MyListIterator getListIterator() {
        return new MyLinkedListImpl();
    }

    private class MyLinkedListImpl implements MyListIterator {

        private Node currentNode;
        private int startIndex;

        public MyLinkedListImpl(int index) {
            startIndex = index;
        }

        public MyLinkedListImpl() {
        }

        public boolean hasNext() {
            return currentNode != tail;
        }

        public Object next() {
            if (currentNode == null) {
                currentNode = getNode(startIndex);
            } else {
                currentNode = currentNode.next;
            }
            return currentNode.value;

        }

        public boolean hasPrevious() {
            return currentNode != head;
        }

        public Object previous() {
            if (currentNode == null) {
                currentNode = getNode(startIndex);
            } else {
                currentNode = currentNode.prev;
            }
            return currentNode.value;
        }

    }

    private void checkOutOfBounds(int index) {
        if (index < 0 || index >= size) {
            String message = String.valueOf(index) + " " + String.valueOf(size);
            throw new IndexOutOfBoundsException(message);
        }

    }

    private void checkOutOfBoundsForAdd(int index) {
        if (index < 0 || index > size) {
            String message = String.valueOf(index) + " " + String.valueOf(size);
            throw new IndexOutOfBoundsException(message);
        }

    }

    private void linkBefore(Object e, Node succ) {

        Node pred = succ.prev;
        Node newNode = new Node(pred, e, succ);
        succ.prev = newNode;
        pred.next = newNode;
        size++;
    }
}
